/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.wider.foundation.db;

import com.wider.foundation.DLogger;
import java.io.File;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *
 * @au thor William Choi <avextk@gmail.com>
 */
public class DBx {
    /***
     * Class Members
     */
    private static DocumentBuilderFactory BldrFactory = null;
    private static DocumentBuilder DOMBuilder = null;

    
    /***
     *  Private Members
     */
    private DLogger         logger;
    private List<File>      cnflst;
    private Document        dbxdom;
    private String          nspace;

    // Key=Namespace : Val=DOM
    private Map<String, Document>   dommap;

    // Key=Query_ID  :  Val=Object of   {DBxConn  or  Map<Namespace, DbxConn>}
    private Map<String, Object>     qrymap;
    

    /***
     *  Constructors
     */
    public DBx(File...confXMLs) throws  ClassNotFoundException, IOException, SAXException, SQLException, Exception{
        cnflst  = Arrays.asList(confXMLs);
        qrymap  = new HashMap<String, Object>();
        dommap = new HashMap<String, Document>();
        init(cnflst);
    }

    /***
     * Public Methods
     */
    public void init(File...confXMLs) throws ClassNotFoundException, IOException, SAXException, SQLException, Exception{
        if (null != confXMLs){
            cnflst = Arrays.asList(confXMLs);
        }
        init(cnflst);
    }

    /***
     * Private Helpers
     */
    private void createDOMBuilder(){
        try {
            // Enable XSD validating so that we can getElementByID
            BldrFactory = DocumentBuilderFactory.newInstance();
            BldrFactory.setNamespaceAware(true);
            BldrFactory.setValidating(true);
            BldrFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", XMLConstants.W3C_XML_SCHEMA_NS_URI);
            BldrFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", new File("resources/DBx.conf.xsd"));
            DOMBuilder = BldrFactory.newDocumentBuilder();
        } catch (ParserConfigurationException ex) {
            logger.error(ex);
        }
    }

    private void init(List<File> confs) throws ClassNotFoundException, IOException, SAXException, SQLException, Exception{
        using(null);
        
        if (null == logger){
            logger = DLogger.getInstance(this);
        }

        // The first DBx instance will help to instanciate an class level XML Document Builder
        if (null == DOMBuilder){
            createDOMBuilder();
        }

        // Parse each XML config file, and configure the DBx instance accordingly
        for (File conf : confs){
            Document    confxml = DOMBuilder.parse(conf);
            Element     xmlroot = (Element) confxml.getElementsByTagName("DBx").item(0);
            String      nmspace = xmlroot.getAttribute("namespace");
            NodeList    dbslist = xmlroot.getElementsByTagName("database");

            // Check if duplicated namespace
            Document    extsdom = dommap.get(nmspace);
            if (null != extsdom){
                throw new Exception("Namespace: " + nmspace + " in configuration file: " + conf.getName() + " already exists.");
            }
            
            // Iterate over the list of databases
            for (int i = 0; i < dbslist.getLength(); i++) {
                Element     database    = (Element) dbslist.item(i);
                NodeList    xqueries    = database.getElementsByTagName("query");

                // Create a new DBxConn instance
                DBxConn     xconnctn    = DBxConn.newDBxConn(nmspace, database);
                
                //Iterate over the queries for this database section
                for (int j = 0; j < xqueries.getLength(); j++){
                    Element xquery  = (Element) xqueries.item(j);
                    String  xqryid  = xquery.getAttribute("id");
                    Object  mapval  = qrymap.get(xqryid);

                    if (null == mapval){ 
                        //This query_id is fresh for this DBx instance, put it into the query map
                        qrymap.put(xqryid, xconnctn);
                    } else{
                        if (mapval instanceof DBxConn){
                            //This query_id already exists in the map
                            DBxConn oldcnn = (DBxConn) mapval;

                            // 1. Check if the existing query belongs to the same namespace as this new query
                            if (nmspace.equalsIgnoreCase( oldcnn.getNamespace() )){
                                throw new Exception("Query ID : " + xqryid + " in namespace: " + nmspace + " is duplicated (1).");
                            }

                            // 2. create the 2nd level mapping (namespace to dbxconn)
                            Map<String, DBxConn> nspmap = new HashMap<String, DBxConn>();
                            
                            // 3. Put the name space of the existing query into the 2nd level map
                            nspmap.put(oldcnn.getNamespace(), oldcnn);

                            // 4. Replace the query map entry with the new 2nd level map
                            qrymap.put(xqryid, nspmap);

                            // 5. Get the 2nd level back from the query map, so that we can process it with the same logic below
                            mapval = qrymap.get(xqryid);
                        }

                        if (mapval instanceof Map){
                            //This query_id has been mapped by two level mapping (q_id to namespace to dbxconn)
                            Map<String, DBxConn> nspmap = (Map) mapval;
                        
                            //Check if any query_id with same namespace has already been mapped
                            if (null != nspmap.get(nmspace)){
                                throw new Exception("Query ID : " + xqryid + " in namespace: " + nmspace + " is duplicated (2).");
                            }
                        
                            //Put the namespace that this query_id belongs to into the namespace_map to the connection
                            nspmap.put(nmspace, xconnctn);
                            
                        } //End if -- mapval instanceof Map
                    } //End if -- mapval not null
                }
            }

            //Put the <namespace, DBx DOM root> into the map
            dommap.put(nmspace, confxml);
        } //End of - foreach conf file

    } //End of - method init

    private ResultSet query_fast(String namespace, String query_id, Object...params){

    }

    private ResultSet query_slow(String namespace, String query_id, Object...params){

    }

    public void using(String namespace) {
        nspace = namespace;
    }

    public ResultSet query(String query_id, Object...params){
        return query(nspace, query_id, params);
    }

    public ResultSet query(String namespace, String query_id, Object...params){
        //TODO: Mostly used queries will be carried through PreparedStatement (fast), other queries will be executed by the statement hold by the DBxConn instance.
        // PS. While using PreparedStatement, parameters will be set independently after the statement is prepared. On the other hand, parameters will be serialized
        //          within the SQL string of which to be executed by Statement
        
        // 0. Try to get the preparedStatement for the query_id from the LRU ring map, if entry exists, perform fast query, update the query_id hit count and exit the sub

        // 1. Try to get the DbxConnection from the query_map by using query_id as the key

        // 2. If the query is not unique within the DBx instance (over all configuration files), use the namespace to select the proper DBxConnection

        // 3. Use namespace to get back the configuration DOM

        // 4. Use the query_id to get back the query section indicated by the query_id

        // 5.
    }

    public void debugwalk(){
        logger.debug("Namespace DOM Map size: " + dommap.size());
        
        for (Map.Entry<String, Object> e : qrymap.entrySet()){
            String key = e.getKey();
            Object val = e.getValue();
            String msg = "\n" + key;

            if (val instanceof Map){
                for (Map.Entry<String, DBxConn> ee : ((Map<String, DBxConn>) val).entrySet()){
                    msg += "\n\t" + ee.getKey() + "::" + ee.getValue().getNamespace() + "::" + ee.getValue().getConnectionString() + "::" + dommap.get(ee.getValue().getNamespace()).getElementById(key).getAttribute("description");
                }
            }else if (val instanceof DBxConn){
                DBxConn cn = (DBxConn) val;
                msg += "\n\t" + cn.getNamespace() + "::" + cn.getConnectionString() + "::" + dommap.get(cn.getNamespace()).getElementById(key).getAttribute("description");
            }
            logger.debug(msg);
        }
    }
}
